package myVPN;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Arrays;

import Encryption.AES_CBC;
import Encryption.Aes;
import Encryption.DH;

public class TCPServer {
	private int portNum;
	private String secretVal;

	private String clientMessage;	
	private String serverMessage;

	private ServerSocket welcomeSocket;
	private Socket connectionSocket;
	private ObjectInputStream inFromClient;
	private ObjectOutputStream outToClient;

	private int currSentIdx;
	private int currReceiveIdx;
	
	private boolean protocolSuccess;
	private String RaFromClient;	
	private int b_exp;	
	private String encrypted;
	private String decrypted;
	
	private String processedIn;
	private String processedOut;
	
	public String getProcessedIn() {
		return processedIn;
	}
	public void setProcessedIn(String processedIn) {
		this.processedIn = processedIn;
	}
	public String getProcessedOut() {
		return processedOut;
	}
	public void setProcessedOut(String processedOut) {
		this.processedOut = processedOut;
	}	
	public int getCurrSentIdx() {
		return currSentIdx;
	}
	public void setCurrSentIdx(int currSentIdx) {
		this.currSentIdx = currSentIdx;
	}
	public int getCurrReceiveIdx() {
		return currReceiveIdx;
	}
	public void setCurrReceiveIdx(int currReceiveIdx) {
		this.currReceiveIdx = currReceiveIdx;
	}	
	public String getRaFromClient() {
		return RaFromClient;
	}
	public void setRaFromClient(String raFromClient) {
		RaFromClient = raFromClient;
	}
	public int getPortNum() {
		return portNum;
	}
	public void setPortNum(int portNum) {
		this.portNum = portNum;
	}
	public String getSecretVal() {
		return secretVal;
	}
	public void setSecretVal(String secretVal) {
		this.secretVal = secretVal;
	}
	public String getClientMessage() {
		return clientMessage;
	}
	public void setClientMessage(String clientMessage) {
		this.clientMessage = clientMessage;
	}
	public String getServerMessage() {
		return serverMessage;
	}
	public void setServerMessage(String replyMessage) {
		this.serverMessage = replyMessage;
	}	
	public boolean isProtocolSuccess() {
		return protocolSuccess;
	}
	public void setProtocolSuccess(boolean protocolSuccess) {
		this.protocolSuccess = protocolSuccess;
	}
	
	public String getEncrypted() {
		return encrypted;
	}
	public void setEncrypted(String encrypted) {
		this.encrypted = encrypted;
	}
	public String getDecrypted() {
		return decrypted;
	}
	public void setDecrypted(String decrypted) {
		this.decrypted = decrypted;
	}
	
	// Constructor
	public TCPServer(){
	}
	public TCPServer(int num, String secret){
		portNum = num;
		secretVal = secret;
	}
	
	// Methods
	public void initConnection() throws Exception{
		welcomeSocket = new ServerSocket(portNum);
	}
	public void startConnection() throws Exception{
		connectionSocket = welcomeSocket.accept();
	}	
	
	public void doKeyExchangeOne() throws Exception{
		b_exp = genRandomInt();
		int b_public = DH.base_4.GetPublicKey(b_exp);

		String myIP_s = "";
		String Rb = genRandomString();
		
		try {
			InetAddress myIP = InetAddress.getLocalHost();
			myIP_s = myIP.getHostAddress();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		serverMessage = Rb + "/" + Aes.beforeEncryption(myIP_s, RaFromClient, b_public, secretVal);
		
		AES_CBC x = new AES_CBC();
		x.Encrypt(Aes.beforeEncryption(myIP_s, RaFromClient, b_public, secretVal));
		processedOut = Rb + "/" + Arrays.toString(x.EncodeEncIV());
		
		byte[] rb_b = (Rb + "/").getBytes();
		final int alen = rb_b.length;
	    final int bclen = x.EncodeEncIV().length;
	    
	    final byte[] result = new byte[alen + bclen];

		System.arraycopy(rb_b, 0, result, 0, alen);
		System.arraycopy(x.EncodeEncIV(), 0, result, alen, bclen);
		
		outToClient.writeObject(result);
		
		
		
		//byte[] encrypted = Aes.EncryptedThis(myIP_s, RaFromClient, b_public, secretVal);
		//String sendStr = Rb + "/" + new String(encrypted);
		
		setCurrReceiveIdx(2);
		setCurrSentIdx(2);
		//processedOut = sendStr;
		
		//outToClient.writeObject(sendStr);
		outToClient.flush();
	}
	
	public int getSessionKey() throws Exception{
		String plaintext = Aes.DecryptNoPadding(encrypted);
		String delims = "/";
		String[] tokens = plaintext.split(delims);
		int responseKey = Integer.parseInt(tokens[2]);
		
		return DH.GetSessionKey(b_exp, responseKey);
	}
	
	public String getSecretKey() throws Exception{
		//String plaintext = Aes.DecryptNoPadding(encrypted);
		String plaintext = encrypted;
		String delims = "/";
		String[] tokens = plaintext.split(delims);
		return tokens[3];
	}
	

	public void streamData() throws Exception{
		// read data from client
		inFromClient = new ObjectInputStream(connectionSocket.getInputStream());
		
		// send data to client
		outToClient = new ObjectOutputStream(connectionSocket.getOutputStream());
		outToClient.flush();		
		
	}
	
	public void readMessage() throws Exception{
		if(protocolSuccess)
		{
			AES_CBC x = new AES_CBC();
			byte[] inbytes = (byte[]) inFromClient.readObject();
			clientMessage =  Arrays.toString(inbytes);
			processedIn = new String(x.DecodeAndDecrypt(inbytes, secretVal));
		}
		
		else if (currReceiveIdx == 1){
			AES_CBC x = new AES_CBC();
			byte[] inbytes = (byte[]) inFromClient.readObject();
			clientMessage =  Arrays.toString(inbytes);
			processedIn = new String(x.DecodeAndDecrypt(inbytes));
		}
		else
		{
		  clientMessage = (String) inFromClient.readObject();
		  processedIn = clientMessage;
		}
	}
	
	public void sendMessage() throws Exception{
		AES_CBC x = new AES_CBC();
		byte[] encodedBytes = x.EncryptAndEncode(serverMessage, secretVal);
		processedOut = Arrays.toString(encodedBytes);
		outToClient.writeObject(encodedBytes);
		outToClient.flush();
	}
	
	public void closeConnection() throws Exception{
		inFromClient.close();
		outToClient.close();
		connectionSocket.close();
		welcomeSocket.close();
	}
	
	private int genRandomInt(){
		return (int) (Math.random() * 99);//Integer.MAX_VALUE);
	}
	private String genRandomString(){
		return Integer.toString((int) (Math.random() * 9999));//Integer.MAX_VALUE));
	}

}
